package config

import (
	"errors"
	"fmt"
	"reflect"

	"github.com/go-viper/mapstructure/v2"
	"github.com/spf13/viper"
)

// ScanTo 从全局配置顶层开始扫描配置内容到 refVal
//   - refVal: 接收对象指针
//   - 不同于Unmarshal方法，Scan方法会忽略refVal未定义的字段, Unmarshal方法如两端字段不一致，会报错或返回空
func ScanTo(refVal any, opts ...viper.DecoderConfigOption) error {
	defaultMu.RLock()
	defer defaultMu.RUnlock()
	if refVal == nil {
		return errors.New("config:ScanTo('refVal') refVal can not a nil pointer")
	}
	// 检查参数是否为指针类型
	if reflect.TypeOf(refVal).Kind() != reflect.Ptr {
		return errors.New("config:ScanTo('refVal') refVal should be a pointer")
	}
	return decode(AllSettings(), defaultDecoderConfig(refVal, opts...))
}

// ScanKey 从指定 Key 读取配置项后扫描到 refVal 中， Key不区分大小写
//   - refVal: 接收对象指针
//   - 不同于 UnmarshalKey 方法，Scan方法会忽略refVal未定义的字段, UnmarshalKey 方法如两端字段不一致，会报错或返回空
func ScanKey(key string, refVal any, opts ...viper.DecoderConfigOption) error {
	defaultMu.RLock()
	defer defaultMu.RUnlock()
	if !deafult().IsSet(key) {
		return fmt.Errorf("config:ScanKey('%s','refVal') key does not exist", key)
	}
	if refVal == nil {
		return fmt.Errorf("config:ScanKey('%s','refVal') refVal can not a nil pointer", key)
	}
	// 检查参数是否为指针类型
	if reflect.TypeOf(refVal).Kind() != reflect.Ptr {
		return fmt.Errorf("config:ScanKey('%s','refVal') refVal should be a pointer", key)
	}
	return decode(Get(key), defaultDecoderConfig(refVal, opts...))
}

// decode is a wrapper around mapstructure.Decode that mimics the WeakDecode functionality.
// github.com/spf13/viper/viper.go
func decode(input any, config *mapstructure.DecoderConfig) error {
	decoder, err := mapstructure.NewDecoder(config)
	if err != nil {
		return err
	}
	return decoder.Decode(input)
}

// defaultDecoderConfig returns default mapstructure.DecoderConfig with support
// of time.Duration values & string slices.
// github.com/spf13/viper/viper.go
func defaultDecoderConfig(output any, opts ...viper.DecoderConfigOption) *mapstructure.DecoderConfig {
	decodeHook := defaultDecodeHook
	if decodeHook == nil {
		decodeHook = mapstructure.ComposeDecodeHookFunc(
			mapstructure.StringToTimeDurationHookFunc(),
			mapstructure.StringToSliceHookFunc(","),
		)
	}

	c := &mapstructure.DecoderConfig{
		Metadata:         nil,
		WeaklyTypedInput: true,
		DecodeHook:       decodeHook,
	}

	for _, opt := range opts {
		opt(c)
	}

	// Do not allow overwriting the output
	c.Result = output

	return c
}
